fix(api): /capabilities terminal-tier marker — null upgrade_url + is_terminal_tier (DOG-26)#185
Merged
mastermanas805 merged 1 commit intoMay 30, 2026
Conversation
…terminal_tier (DOG-26) Today every tier including Team returns upgrade_url="https://instanode.dev/pricing/". Team is the top of the rank ladder — there is nothing to upgrade to. SDKs/dashboards rendering an Upgrade CTA on the Team plan show a button that loops back to the pricing page with no action. Now: top-rank tier emits upgrade_url=null + is_terminal_tier=true. Every other tier keeps the pricing URL + is_terminal_tier=false. Clients can suppress the Upgrade CTA via the boolean (or "URL is null" check) without string-matching "team" — adding an enterprise tier above team tomorrow Just Works. Done via rank-iterating the live registry (capabilities.go computes terminalRank as entries[len-1].rank after the rank sort), so the contract is anchored to plans.Rank, not to a hardcoded tier name. OpenAPI: upgrade_url is now ["string", "null"]; is_terminal_tier is a required boolean. Both schema changes are non-breaking — existing clients that read upgrade_url as a string get null for Team only. Coverage block: Symptom: upgrade_url for terminal tier (Team) loops to pricing Enumeration: grep upgrade_url + UpgradeURL in api/internal/handlers/ for capabilities-specific sites (1 emit + 1 test + 1 openapi) Sites found: 3 Sites touched: 3 (capabilities.go emit, capabilities_test.go assert, openapi.go schema + required list) Coverage test: TestCapabilities_TerminalTierUpgradeURLIsNull — registry-iterating (rule 18): asserts entries[len-1] has IsTerminalTier=true + UpgradeURL=nil, and every other tier is the inverse. Adding a new top tier automatically shifts the assertion target. Live verified: pending merge + auto-deploy + curl /api/v1/capabilities | jq '.tiers[] | {tier, upgrade_url, is_terminal_tier}' Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
f551e4d to
582e9a2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
upgrade_url=null+is_terminal_tier=trueso SDKs/dashboards can suppress the Upgrade CTA without string-matching"team"is_terminal_tier=falseentries[len-1].rank) — adding anenterprisetier above Team tomorrow Just WorksRule-17 coverage block
upgrade_urlfor the terminal Team tier loops to/pricing/(no destination)upgrade_url+UpgradeURLinapi/internal/handlers/(capabilities-specific sites)capabilities.goemit,capabilities_test.goassert,openapi.goschemaTestCapabilities_TerminalTierUpgradeURLIsNull— registry-iterating (rule 18): assertsbody.Tiers[len-1].IsTerminalTier == true+UpgradeURL == nil, every other tier is the inverse. Doesn't hardcode"team"as the terminal name — works as the ladder evolves.curl https://api.instanode.dev/api/v1/capabilities | jq '.tiers[] | {tier, upgrade_url, is_terminal_tier}'OpenAPI contract
upgrade_url:string→["string", "null"](non-breaking — only Team gets null)is_terminal_tier: new required booleanTest plan
go build ./...go vet ./...go test ./internal/handlers/ -short -count=1 -run TestCapabilities(all 8 cases pass)go test ./internal/handlers/ -short -count=1 -run TestOpenAPI(schema valid)curl /api/v1/capabilities | jq '.tiers[] | select(.tier=="team") | .upgrade_url'returnsnull